package com.bsj.media.media;

import com.bsj.media.media.G726.G726_32k;
import com.bsj.media.media.G726.g726_state_s;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.javacpp.PointerPointer;
import org.bytedeco.javacpp.swresample.*;

import static org.bytedeco.javacpp.avutil.*;
import static org.bytedeco.javacpp.swresample.*;

/**
 * 对讲音频编码
 * 
 * @author Chan
 *
 */
@Slf4j
public class PcmEncoderAudio2 {



	private SwrContext samples_convert_ctx;

	private int samples_size;
	private BytePointer[] samples_out2;
	private BytePointer[] samples_in2;
	private PointerPointer<BytePointer> samples_in_ptr;
	private PointerPointer<BytePointer> samples_out_ptr;

	// private byte[] needSampleBuf = null; // 需要采样的样本数buffer
	private int needSampleNum = 0; // 需要的样本数

	// private int lastSampleNum = 0; // 需要采样buffer中已有采样数
	private int srcSampleNum = 0; // 编码一帧aac，需要源音频的样本数

	// 是否初始化
	private boolean isInit;
	// 时间戳
	private long timestamp;

	// APDCM_IMA 编码对象
	private ima_adpcm_state_s adpcmState;

	// G726_32K 编码对象
	private g726_state_s g726State;

	// 编码类型
	private int m_audioType;

	public PcmEncoderAudio2() {
		super();
		this.setInit(false);
		this.setTimestamp(0);
	}

	/**
	 * 编码类初始化
	 * 
	 * @param audioType
	 *            需要编码的类型
	 * @param outChannels
	 * @param outSampleRate
	 * @param outBitRate
	 * @param inChannels
	 * @param inSampleRate
	 * @param inAudioSampleType
	 *            输入音频的格式
	 * @return
	 */
	public boolean Init(int audioType, int outChannels, int outSampleRate, int outBitRate, int inChannels, int inSampleRate, int inAudioSampleType) {
		int ret = -1;

		if (audioType == 0) {
			// ADPCM_IMA
			adpcmState = new ima_adpcm_state_s();
		} else if (audioType == 1) {
			// G711A
		} else if (audioType == 2) {
			g726State = G726_32k.g726_init();
		} else {
			log.warn("对讲，获取设备音频类型失败！当前不支持编码类型：" + audioType);
			return false;
		}

		// 如果输入输出通道数，采样率，音频PCM格式与编码需要的格式相同，不需要重采样
		if (outChannels != inChannels || outSampleRate != inSampleRate || inAudioSampleType != AV_SAMPLE_FMT_S16) {
			// 初始化重采样
			samples_convert_ctx = swr_alloc_set_opts(samples_convert_ctx, av_get_default_channel_layout(outChannels),
					AV_SAMPLE_FMT_S16, outSampleRate, av_get_default_channel_layout(inChannels), inAudioSampleType,
					inSampleRate, 0, null);

			if (samples_convert_ctx == null) {
				log.error("swr_alloc_set_opts() error: Cannot allocate the conversion context.");
				return false;
			} else if ((ret = swr_init(samples_convert_ctx)) < 0) {
				log.error("swr_init() error " + ret + ": Cannot initialize the conversion context.");
				return false;
			}
		}

		// G711a ,G726_32K编码8000采样率一帧需要320个样本
		int nb_samples = 320;

		// 编码一帧需要的输入采样率所需样本数,G711a ,G726_32K编码8000采样率一帧需要320个样本
		srcSampleNum = (int) (nb_samples * ((float) inSampleRate / outSampleRate));
		// 3是客户端32位的，其他为16位
		int bytePerSample = inAudioSampleType == 3 ? 4 : 2;
		needSampleNum = srcSampleNum * inChannels * bytePerSample;
		// 输出样本数
		samples_size = outChannels * nb_samples * 2;

		samples_in_ptr = new PointerPointer<BytePointer>(1); // AV_SAMPLE_FMT_FLP
		samples_out_ptr = new PointerPointer<BytePointer>(2); // AV_SAMPLE_FMT_S16

		samples_out2 = new BytePointer[1];
		samples_out2[0] = new BytePointer(av_malloc(samples_size));

		samples_in2 = new BytePointer[1];
		samples_in2[0] = new BytePointer(av_malloc(needSampleNum));

		this.m_audioType = audioType;
		this.isInit = true;

		return true;
	}

	/**
	 * 
	 * @param srcPcm
	 * @param srcLen
	 * @param src_nbSamples
	 * @param dst
	 * @param dstOffset
	 * @param dstLen
	 * @return
	 */
	public int Encodec(byte[] srcPcm, int srcLen, int src_nbSamples, byte[] dst, int dstOffset, int dstLen) {
		int ret = -1;
		int frame_size = 320;
		for (int i = 0; i < samples_out2.length; i++) {
			samples_out_ptr.put(i, samples_out2[i].position(0));
		}

		byte[] samplePcm = null;
		int sampleLen = 0;
		if (samples_convert_ctx != null) {
			samples_in2[0].put(srcPcm, 0, srcLen);
			samples_in_ptr.put(0, samples_in2[0].position(0));

			// samples_convert_ctx 不等于null，意味着需要编码
			ret = swr_convert(samples_convert_ctx, samples_out_ptr, frame_size, samples_in_ptr, src_nbSamples);
			if (ret <= 0) {
				log.error("swr_convert() error " + ret + ": Cannot convert audio samples.");
				return ret;
			}

			sampleLen = ret * 2;
			samplePcm = new byte[sampleLen];
			samples_out2[0].get(samplePcm, 0, sampleLen);
		} else {
			samplePcm = srcPcm;
			sampleLen = srcLen;
		}

		if (m_audioType == 0) {
			// adpcm
			ret = Adpcm.encoder(adpcmState, dst, dstOffset, samplePcm, sampleLen);
		} else if (m_audioType == 1) {
			// g711a
			ret = G711A.encoder(samplePcm, sampleLen, dst, dstOffset);
		} else if (m_audioType == 2) {
			// g726
			ret = G726_32k.g726_encode(g726State, dst, dstOffset, samplePcm, sampleLen);
		}

		return (ret + dstOffset);
	}

	public void release() {
		if (samples_convert_ctx != null) {
			swr_free(samples_convert_ctx);
			samples_convert_ctx = null;
		}

		if (samples_out2 != null) {
			for (int i = 0; i < samples_out2.length; i++) {
				av_free(samples_out2[i].position(0));
			}
			samples_out2 = null;
		}
		if (samples_in2 != null) {
			for (int i = 0; i < samples_in2.length; i++) {
				av_free(samples_in2[i].position(0));
			}
		}
	}

	public boolean isInit() {
		return isInit;
	}

	public void setInit(boolean isInit) {
		this.isInit = isInit;
	}

	public long getTimestamp() {
		return timestamp;
	}

	public void setTimestamp(long timestamp) {
		this.timestamp = timestamp;
	}

	public int getNeedSampleNum() {
		return needSampleNum;
	}

	public void setNeedSampleNum(int needSampleNum) {
		this.needSampleNum = needSampleNum;
	}

	public int getSrcSampleNum() {
		return srcSampleNum;
	}
}
